﻿using System;
using System.Threading;

namespace Helpers.Managers
{
    /// <summary>
    /// Provides a base class for a manager class with a timer which executes periodically a specific method.
    /// Managers.ManagerBase must be derived from when creating a new class.
    /// </summary>
    public abstract class ManagerBase : IManager
    {
        #region Properties

        /// <summary>
        /// The main timer of the manager.
        /// </summary>
        protected Timer MainTimer;
        /// <summary>
        /// Behind value of variable MainTimerCallbackExecuting.
        /// </summary>
        protected bool MainTimerCallbackExecuting;
        /// <summary>
        /// The name of the manager.
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// Behind value of variable Running.
        /// </summary>
        private bool running = false;
        /// <summary>
        /// Indicates if the main timer of the manager is running.
        /// </summary>
        public bool Running
        {
            get
            {
                /* Supports multithread. */
                lock (this.RunningLock)
                    return this.running;
            }
            protected set
            {
                /* Supports multithread. */
                lock (this.RunningLock)
                {
                    this.running = value;

                    if (this.running == false)
                    {
                        /* Indicates to other methods that the main timer is not stopping anymore. */
                        this.Stopping = false;
                    }
                }
            }
        }
        /// <summary>
        /// Used to lock the variable Running for multithread support.
        /// </summary>
        protected readonly object RunningLock = new object { };
        /// <summary>
        /// Behind value of variable Stopping.
        /// </summary>
        private bool stopping = false;
        /// <summary>
        /// Indicates if the main timer of the manager is being stopped.
        /// </summary>
        protected bool Stopping
        {
            get
            {
                /* Supports multithread. */
                lock (this.StoppingLock)
                    return this.stopping;
            }
            set
            {
                /* Supports multithread. */
                lock (this.StoppingLock)
                    this.stopping = value;
            }
        }
        /// <summary>
        /// Used to lock the variable Stopping for multithread support.
        /// </summary>
        protected readonly object StoppingLock = new object { };


        /// <summary>
        /// The interval of time of the manager timer.
        /// </summary>
        public int TimerInterval { get; set; }

        #endregion

        #region Static fields

        internal static int instanciesCount = -1;

        /// <summary>
        /// The default vale of the interval of timer of the manager timers.
        /// </summary>
        public const int DEFAULT_TIMER_INTERVAL = 60000;

        #endregion

        /// <summary>
        /// Initializes a new instance of the class.
        /// </summary>
        public ManagerBase()
        {
            this.Initialize();
        }

        #region Methods

        internal void Initialize()
        {
            this.TimerInterval = ManagerBase.DEFAULT_TIMER_INTERVAL;

            this.Name = this.GetType().UnderlyingSystemType.Name + "-" + (++ManagerBase.instanciesCount).ToString();
        }

        /// <summary>
        /// The method which the main timer executes periodically. This method must be implemented on derived classes.
        /// </summary>
        protected virtual void MainLoopHandler()
        {
            throw new NotImplementedException();
        }

        #region IManager Members

        /// <summary>
        /// Releases all resources used by the current instance of <typeparamref name="Managers.ManagerBase"/>.
        /// </summary>
        public virtual void Dispose()
        {
            /* If the manager stills running stops it. */
            if (this.Running == true)
            {
                /* IFthe manager did not start to stop does it. */
                if (this.Stopping == false)
                    this.Stop();

                DateTime start = DateTime.UtcNow;
                int timeout = 30000;

                /* Waits for the main timer to stop. */
                while ((this.MainTimerCallbackExecuting == true) && ((DateTime.UtcNow - start).TotalMilliseconds < timeout))
                {
                    Thread.Sleep(100);
                }
            }

            if (this.MainTimer != null)
                this.MainTimer.Dispose();
        }
        /// <summary>
        /// Stars the main timer of the manager.
        /// </summary>
        public virtual void Start()
        {
            try
            {
                /* Do not start it if is just running. */
                if (this.Running == false)
                {
                    /* if the main timer is null, initializes it; else just start it. */
                    if (this.MainTimer == null)
                    {
                        this.MainTimer = new Timer(
                            new TimerCallback(this.MainTimer_Callback)
                            , null
                            , 0
                            , this.TimerInterval
                        );
                    }
                    else
                    {
                        this.MainTimer.Change(this.TimerInterval, this.TimerInterval);
                    }

                    /* Indicates that the main timer is running to prevent start it again. */
                    this.Running = true;
                }
            }
            catch(Exception ex)
            {
                throw new Exception(
                    string.Format(
                        "Failure starting '{0}' manager.",
                        this.Name
                    ),
                    ex
                );
            }
        }
        /// <summary>
        /// Stops the main timer of the manager.
        /// </summary>
        public virtual void Stop()
        {
            try
            {
                /* 
                    Indicates that the main timer must stop. The main timer's callback
                will ask for this property on each execution.
                */
                this.Stopping = true;

                /* Stops main timer to prevent future execution. But remember that maybe the timer was just executing. */
                this.MainTimer.Change(Timeout.Infinite, Timeout.Infinite);

                /* But if the main timer just stopped indicates that the manager is not running, what is true. */
                if (this.MainTimerCallbackExecuting == false)
                {
                    /* Indicates to other methods that the main timer is not running. */
                    this.Running = false;
                }
            }
            catch (Exception ex)
            {
                throw new Exception(string.Format("Failure stopping '{0}' manager.", this.Name), ex);
            }
        }

        #endregion

        #endregion

        #region Events

        /// <summary>
        /// Main timer callback.
        /// </summary>
        /// <param name="state">An object containing information to be used by the callback method, or null.</param>
        protected virtual void MainTimer_Callback(object state)
        {
            lock (this.MainTimer)
            {
                if (this.MainTimerCallbackExecuting == false)
                {
                    this.MainTimerCallbackExecuting = true;
                }
                else
                {
                    return;
                }
            }

            try
            {
                /* Asks for Stopping property to prevent execute the MainLoopHandler if the manager is stopping. */
                if (this.Stopping == false)
                {
                    /* -------------------------------------------------- */
                    /* ----------- Executes main loop handler ----------- */
                    /* -------------------------------------------------- */

                    this.MainLoopHandler();

                    /* -------------------------------------------------- */
                    /* -------------------------------------------------- */

                    if (this.Stopping == true)
                    {
                        /* Indicates to other methods that the main timer is not running. */
                        this.Running = false;
                    }
                }
                else
                {
                    /* Indicates to other methods that the main timer is not running. */
                    this.Running = false;
                }
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message, ex);
            }
            finally
            {
                this.MainTimerCallbackExecuting = false;
            }
        }

        #endregion
    }
}
